package com.googlecode.afdbaccess.web.common.login;

import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.LoginException;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

import com.googlecode.afdbaccess.cache.DbCache;
import com.googlecode.afdbaccess.cache.TransactionAutoCommit;
import com.googlecode.afdbaccess.cache.TransactionBase;
import com.googlecode.afdbaccess.cache.resultconversion.exceptions.DataLayerWrappedResultSetConversionException;
import com.googlecode.afdbaccess.cache.resultconversion.exceptions.NoRowsReturnedException;

@RunWith(MockitoJUnitRunner.class)
public class TransactedTaskTest {
    @Test
    public void testHappy() throws Exception  {
        // Given
        DbCache dbCacheMock = Mockito.mock(DbCache.class);
        
        final String returnString = "returnedString";

        TransactedTask<String> task = new TransactedTask<String>() {
            @Override
            protected String perform(TransactionBase t, String userName) {
                return returnString;
            }
        };

        // When
        String actualReturnedString = task.performWithErrorHandlingAndCleanup(dbCacheMock, "theSource", "theUserName");

        // Then
        Assert.assertEquals(returnString, actualReturnedString);
    }
    
    // Exception cases
    
    @Test
    public void testRuntimeException() throws Exception {
        Exception thrownException = new RuntimeException("Some message");
        testExceptionHandler(thrownException, LoginException.class);
    }
    
    @Test
    public void testCheckedException() throws Exception {
        Exception thrownException = new Exception("Some message");
        testExceptionHandler(thrownException, LoginException.class);
    }

    @Test
    public void testNoRowsReturned() throws Exception  {
        Exception thrownException = new DataLayerWrappedResultSetConversionException(new NoRowsReturnedException("Some message"));
        testExceptionHandler(thrownException, AccountNotFoundException.class);
    }
    
    @Test
    public void testWrappedRuntimeException() throws Exception {
        Exception thrownException = new DataLayerWrappedResultSetConversionException(new RuntimeException("Some message"));
        testExceptionHandler(thrownException, LoginException.class);
    }
    
    @Test
    public void testWrappedCheckedException() throws Exception {
        Exception thrownException = new DataLayerWrappedResultSetConversionException(new Exception("Some message"));
        testExceptionHandler(thrownException, LoginException.class);
    }

    //
    
    private void testExceptionHandler(final Exception thrownException, Class<? extends Exception> expectedException) throws Exception {
        // Given
        DbCache dbCacheMock = Mockito.mock(DbCache.class);
        final TransactionAutoCommit tMock = Mockito.mock(TransactionAutoCommit.class);
        
        TransactedTask<String> task = new TransactedTask<String>() {
            @Override
            protected TransactionAutoCommit getNewTransaction(DbCache dbCache, String source) {
                return tMock;
            }

            @Override
            protected String perform(TransactionBase t, String userName) throws Exception {
                throw thrownException;
            }
        };
        
        // When
        try {
            task.performWithErrorHandlingAndCleanup(dbCacheMock, "theSource", "theUserName");
        } catch (Exception exception) {
            // Then
            if(expectedException.isInstance(exception)) {
                assertAfterException(tMock, thrownException, exception);
                return;
            } else {
                throw exception;
            }
        }
        
        Assert.fail("Should have gotten " + expectedException.getCanonicalName());
    }
    
    private void assertAfterException(TransactionAutoCommit tMock, final Exception thrownException, Exception exception) {
        Assert.assertEquals(thrownException, exception.getCause());
        
        Mockito.verify(tMock).close();
        Mockito.verifyNoMoreInteractions(tMock);
    }
}
